/****************************************************************
 * <strong>MFDataRows虚拟化数据表</strong>
 *
 * <p>
 *MFDataRows虚拟化数据表 
 * </p>
 *
 * @author AndyZhang 2011 cnet2001@163.com
 * @version 0.1
 * ToDo:2011-11-29 支持 BigTable
 ***************************************************************/

package com.thinkwide.data.ormapping.datatable;

import com.thinkwide.data.core.MFString;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.ListIterator;

public class MFDataRows extends ArrayList<MFDataRow> {

    /**
     *
     */
    private static final long serialVersionUID = 1L;
    private MFDataTable dataTable;

    public MFDataRows(MFDataTable dataTable) {
        this.dataTable = dataTable;
    }

    public void join(MFDataRow dataRow, String foreignKey) {
        //to be ..
    }

    public void orderBy(String fieldName, boolean aesc) throws MFDataColumnException {
        for (int x = 0; x < this.size(); x++) {
            int y = this.size();
            for (int i = 0; i < y - 1; i++) {
                MFDataRow a = this.get(i);
                MFDataRow b = this.get(i + 1);


                MFDataColumn col = this.getDataTable().getColumns().getColumn(fieldName);


                if (MFString.compareNoCase(col.getDataType(), "DOUBLE")) {
                    BigDecimal s1 = a.getBigDecimal(fieldName);
                    BigDecimal s2 = b.getBigDecimal(fieldName);
                    //MFDebug.println(s1);
                    int flag = s1.compareTo(s2);


                    if (aesc) {
                        if (flag >= 0) {
                            this.set(i, b);
                            this.set(i + 1, a);
                        }
                    } else {
                        if (flag < 0) {
                            this.set(i, b);
                            this.set(i + 1, a);
                        }
                    }

                } else if (MFString.compareNoCase(col.getDataType(), "Integer")) {
                    Integer s1 = a.getInt(fieldName);
                    Integer s2 = b.getInt(fieldName);
                    //MFDebug.println(s1);
                    int flag = s1.compareTo(s2);

                    if (aesc) {
                        if (flag >= 0) {
                            this.set(i, b);
                            this.set(i + 1, a);
                        }
                    } else {
                        if (flag < 0) {
                            this.set(i, b);
                            this.set(i + 1, a);
                        }
                    }

                } else {
                    String s1 = a.getString(fieldName);
                    String s2 = b.getString(fieldName);
                    //MFDebug.println(s1);
                    int flag = s1.compareToIgnoreCase(s2);


                    if (aesc) {
                        if (flag >= 0) {
                            this.set(i, b);
                            this.set(i + 1, a);
                        }
                    } else {
                        if (flag < 0) {
                            this.set(i, b);
                            this.set(i + 1, a);
                        }
                    }

                }


            }

        }
    }

    public void orderBy(String fieldName) throws MFDataColumnException {
        orderBy(fieldName, true);
    }

    public void addRow(MFDataRow dataRow) {
        dataRow.setDataRows(this);
        add(dataRow);
    }

    public MFDataRow clone(MFDataRow row) throws MFDataColumnException {
        MFDataRow arow = this.newRow();
        arow.setState(row.getState());
        for (MFDataColumn col : this.getDataTable().getColumns()) {
            if (this.getDataTable().getColumns().hasColumn(col.getField()))
                arow.setValue(col.getField(), row.getValue(col.getField()));
        }
        this.add(arow);
        return arow;
    }

    public MFDataRow newRow() {
        return new MFDataRow(this);
    }

    public ArrayList<MFDataRow> select(String expression) throws MFDataColumnException {
        Expression e = Expression.FindExpression(expression);
        ArrayList<MFDataRow> list = new ArrayList<MFDataRow>();

        for (MFDataRow row : this)
            if (e.eval(row))
                list.add(row);
        return list;
    }


    public Object[] collect(String fieldName) throws MFDataColumnException {
        if (this.size() == 0)
            return null;

        LinkedList<Object> list = new LinkedList<Object>();
        ListIterator<MFDataRow> iter = this.listIterator();

        while (iter.hasNext())
            list.add(iter.next().getValue(fieldName));

        return list.toArray();
    }


    public void setDataTable(MFDataTable dataTable) {
        this.dataTable = dataTable;
    }


    public MFDataTable getDataTable() {
        return dataTable;
    }

}


//<editor-fold defaultstate="collapsed" desc="Expression">
class Expression {
    private String expression;
    private int exp_len;
    private char[] exp_array;
    private ArrayList<String> token_list;
    private boolean is_flushed;
    private char c;
    private StringBuffer sb;
    private int cur_token_index;
    private int token_count;

    public Expression(String expression) {
        this.expression = expression;
        this.exp_len = expression.length();
        this.exp_array = expression.toCharArray();
        this.token_list = new ArrayList<String>();
        this.is_flushed = true;
        this.cur_token_index = -1;
        this.sb = new StringBuffer();
        this.initTokenList();
        this.token_count = this.token_list.size();

    }

    private void flush() {
        if (this.is_flushed)
            return;

        this.token_list.add(this.sb.toString());
        this.sb = new StringBuffer();
        this.is_flushed = true;
    }

    private void collect() {
        this.sb.append(c);
        this.is_flushed = false;
    }

    private boolean nextToken() {

        if (this.cur_token_index < this.token_count - 1) {
            this.cur_token_index++;
            return true;
        }

        return false;
    }

    private String currentToken() {
        return this.token_list.get(this.cur_token_index);
    }

    private void initTokenList() {
        for (int i = 0; i < this.exp_len; i++) {
            c = exp_array[i];
            if (Character.isWhitespace(c)) {
                this.flush();
                continue;
            }

            switch (c) {
                case '<':

                    this.flush();
                    if (i + 1 < this.exp_len && exp_array[i + 1] == '=') {
                        this.token_list.add("<=");
                        i++;
                    } else {
                        this.token_list.add("<");
                    }
                    break;

                case '>':

                    this.flush();
                    if (i + 1 < this.exp_len && exp_array[i + 1] == '=') {
                        this.token_list.add(">=");
                        i++;
                    } else {
                        this.token_list.add(">");
                    }
                    break;

                case '(':

                    this.flush();
                    this.token_list.add("(");
                    break;

                case ')':

                    this.flush();
                    this.token_list.add(")");
                    break;

                case '=':

                    if (i + 1 < this.exp_len && exp_array[i + 1] == '=') {
                        this.flush();
                        this.token_list.add("==");
                        i++;
                    } else {
                        this.collect();
                    }
                    break;

                case '!':

                    if (i + 1 < this.exp_len && exp_array[i + 1] == '=') {
                        this.flush();
                        this.token_list.add("!=");
                        i++;
                    } else {
                        this.collect();
                    }
                    break;


                default:

                    this.collect();
                    break;
            }
        }

        this.flush();
    }

    public boolean eval(MFDataRow dr) throws MFDataColumnException {
        this.nextToken();
        boolean result = this.doAndOr(dr);
        this.cur_token_index = -1;
        return result;
    }

    private boolean doAndOr(MFDataRow dr) throws MFDataColumnException {
        boolean result = this.doNot(dr);
        String op = "";

        boolean result_right;

        while ((op = this.currentToken()).equals("and") || op.equals("or")) {
            this.nextToken();

            if (op.equals("and")) {
                result_right = this.doNot(dr);
                result = result && result_right;
            } else {
                result_right = this.doNot(dr);
                result = result || result_right;
            }
        }

        return result;
    }

    private boolean doNot(MFDataRow dr) throws MFDataColumnException {
        String op = "";

        if ((op = this.currentToken()).equals("not"))
            this.nextToken();

        boolean result = this.doBrackets(dr);

        if (op.equals("not"))
            return !result;

        return result;
    }

    private boolean doBrackets(MFDataRow dr) throws MFDataColumnException {
        boolean result;
        if (this.currentToken().equals("(")) {
            this.nextToken();
            result = this.doAndOr(dr);
            this.nextToken();
        } else {
            result = this.doCompare(dr);
        }

        return result;
    }

    private boolean doCompare(MFDataRow dr) throws MFDataColumnException {
        Object field = dr.getValue(this.currentToken());
        this.nextToken();
        String opt = this.currentToken();
        this.nextToken();
        String value = this.currentToken();
        this.nextToken();

        if (opt.equals("like")) {
            return isLike(field, value);
        } else if (opt.equals(">")) {
            return isGreat(field, value);
        } else if (opt.equals("<")) {
            return isLess(field, value);
        } else if (opt.equals("==")) {
            return isEquals(field, value);
        } else if (opt.equals(">=")) {
            return isGreatEquals(field, value);
        } else if (opt.equals("<=")) {
            return isLessEquals(field, value);
        } else if (opt.equals("!=")) {
            return isNotEquals(field, value);
        }

        return false;

    }


//<editor-fold  defaultstate="collapsed" desc="tool method">

    private static boolean isLike(Object field, String value) {
        int len = value.length();
        if (value.startsWith("'%") && value.endsWith("%'"))
            return MFDTConvert.toString(field).contains(value.substring(2, len - 2));
        else if (value.startsWith("'%"))
            return MFDTConvert.toString(field).endsWith(value.substring(2, len - 1));
        else if (value.endsWith("%'"))
            return MFDTConvert.toString(field).startsWith(value.substring(1, len - 2));
        else
            return MFDTConvert.toString(field).equals(value.substring(1, len - 1));
    }

    private static boolean isLess(Object field, String value) {
        if (field instanceof Number)
            return MFDTConvert.toFloat(field) < MFDTConvert.toFloat(value);
        return MFDTConvert.toString(field).compareTo(value.substring(1, value.length() - 1)) < 0;
    }

    private static boolean isGreat(Object field, String value) {
        if (field instanceof Number)
            return MFDTConvert.toFloat(field) > MFDTConvert.toFloat(value);
        return MFDTConvert.toString(field).compareTo(value.substring(1, value.length() - 1)) > 0;
    }

    private static boolean isEquals(Object field, String value) {
        if (value.equals("null"))
            return field == null;

        if (field instanceof Number)
            return MFDTConvert.toFloat(field) == MFDTConvert.toFloat(value);
        if (field instanceof Boolean)
            return MFDTConvert.toBool(field) == MFDTConvert.toBool(value);

        return MFDTConvert.toString(field).equals(value.substring(1, value.length() - 1));
    }

    private static boolean isNotEquals(Object field, String value) {
        if (value.equals("null"))
            return field != null;

        if (field instanceof Number)
            return MFDTConvert.toFloat(field) != MFDTConvert.toFloat(value);
        if (field instanceof Boolean)
            return MFDTConvert.toBool(field) == MFDTConvert.toBool(value);

        return !MFDTConvert.toString(field).equals(value.substring(1, value.length() - 1));
    }

    private static boolean isLessEquals(Object field, String value) {
        if (field instanceof Number)
            return MFDTConvert.toFloat(field) <= MFDTConvert.toFloat(value);
        return MFDTConvert.toString(field).compareTo(value.substring(1, value.length() - 1)) <= 0;
    }

    private static boolean isGreatEquals(Object field, String value) {
        if (field instanceof Number)
            return MFDTConvert.toFloat(field) >= MFDTConvert.toFloat(value);
        return MFDTConvert.toString(field).compareTo(value.substring(1, value.length() - 1)) >= 0;
    }
//</editor-fold>

    private static HashMap<String, Expression> expression_map = new HashMap<String, Expression>();

    public static Expression FindExpression(String expression) {
        Expression e = expression_map.get(expression);
        if (e == null) {
            e = new Expression(expression);
            expression_map.put(expression, e);
        }
        return e;
    }

}
//</editor-fold>
